package org.tbyrne.motion
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.utils.Dictionary;
	
	[Event(type="org.tbyrne.motion.AtomicMotionEvent",name="valueChange")]
	[Event(type="org.tbyrne.motion.AtomicMotionEvent",name="motionEnd")]
	public class AtomicTween extends EventDispatcher
	{
		// Errors
		private static const VALUE_ERROR:String = "AtomicTweens.value can only be accessed on AtomicTweens that operate on one property";
		private static const DEST_ERROR:String = "AtomicTweens.destination can only be accessed on AtomicTweens that operate on one property";
		
		private static const instances:Dictionary = new Dictionary(true);
		private static function stealMotionOn(subject:Object, prop:String):MotionDetails{
			for(var inst:* in instances){
				var cast:AtomicTween = (inst as AtomicTween);
				if(cast.subject==subject){
					var details:MotionDetails = cast.stealMotion(prop);
					if(details)return details;
				}
			}
			return null;
		}
		
		public function get subject():Object{
			return _subject;
		}
		public function get mass():Number{
			return _mass;
		}
		public function set mass(to:Number):void{
			if(_mass != to){
				_mass = to;
				for(var i:int=0; i<motions.length; i++){
					(motions[i] as MotionDetails).motion.mass = to;
				}
				addListeners();
			}
		}
		public function get acceleration():Number{
			return _acceleration;
		}
		public function set acceleration(to:Number):void{
			if(_acceleration != to){
				_acceleration = to;
				for(var i:int=0; i<motions.length; i++){
					(motions[i] as MotionDetails).motion.acceleration = to;
				}
				addListeners();
			}
		}
		public function get decceleration():Number{
			return _decceleration;
		}
		public function set decceleration(to:Number):void{
			if(_decceleration != to){
				_decceleration = to;
				for(var i:int=0; i<motions.length; i++){
					(motions[i] as MotionDetails).motion.decceleration = to;
				}
				addListeners();
			}
		}
		public function get rounding():Number{
			return _rounding;
		}
		public function set rounding(to:Number):void{
			if(_rounding != to){
				_rounding = to;
				for(var i:int=0; i<motions.length; i++){
					(motions[i] as MotionDetails).motion.rounding = to;
				}
				addListeners();
			}
		}
		public function get value():Number{
			if(motions.length==1){
				return (motions[0] as MotionDetails).motion.value;
			}else{
				throw new Error(VALUE_ERROR);
			}
		}
		public function set value(to:Number):void{
			if(motions.length==1){
				(motions[0] as MotionDetails).motion.value = to;
				addListeners();
			}else{
				throw new Error(VALUE_ERROR);
			}
		}
		public function get duration():Number{
			return _duration;
		}
		public function set duration(to:Number):void{
			if(_duration != to){
				_duration = to;
				for(var i:int=0; i<motions.length; i++){
					(motions[i] as MotionDetails).motion.duration = to;
				}
			}
		}
		public function get destination():Number{
			if(motions.length==1){
				return (motions[0] as MotionDetails).motion.destination;
			}else{
				throw new Error(DEST_ERROR);
			}
		}
		public function set destination(to:Number):void{
			if(motions.length==1){
				(motions[0] as MotionDetails).motion.destination = to;
			}else{
				throw new Error(DEST_ERROR);
			}
		}
		
		private var _subject:Object;
		private var _mass:Number;
		private var _duration:Number;
		private var _acceleration:Number;
		private var _decceleration:Number;
		private var _rounding:Number;
		private var motions:Array = [];
		private var longestMotion:AtomicMotion;
		
		public function AtomicTween(subject:Object, prop:*, begin:*, finish:*, duration:Number = NaN, mass:Number=NaN, acceleration:Number=NaN, decceleration:Number=NaN){
			instances.push(this);
			_subject = subject;
			if(prop is String){
				prop = [prop];
			}
			if(begin is Number){
				begin = [begin];
			}
			if(finish is Number){
				finish = [finish];
			}
			if(prop is Array){
				var props:Array = (prop as Array);
				var begins:Array = (begin is Array)?(begin as Array):[];
				var finishes:Array = (finish is Array)?(finish as Array):[];
				for(var i:int=0; i<props.length; i++){
					var thisProp:String = props[i];
					if(thisProp){
						var start:Number = begins[i];
						var end:Number = finishes[i];
						var currentVal:Number = _subject[thisProp];
						
						var details:MotionDetails = stealMotionOn(subject, thisProp);
						if(!details){
							var motion:AtomicMotion = new AtomicMotion();
							motion.value = (!isNaN(start))?start:(!isNaN(currentVal)?currentVal:0);
							details = new MotionDetails(_subject,motion,thisProp);
						}
						details.motion.destination = (!isNaN(end))?end:0;
						motions.push(details);
					}
				}
			}
			
			if(!isNaN(duration)){
				this.duration = duration;
			}
			
			var autoStart:Boolean = true
			if(!isNaN(mass)){
				this.mass = mass;
			}else autoStart = false;
			if(!isNaN(acceleration)){
				this.acceleration = acceleration;
			}else autoStart = false;
			if(!isNaN(decceleration)){
				this.decceleration = decceleration;
			}else autoStart = false;
			
			if(autoStart)this.start();
		}
		public function start():void{
			for(var i:int=0; i<motions.length; i++){
				var details:MotionDetails = (motions[i] as MotionDetails);
				details.motion.start();
			}
			addListeners();
		}
		public function stop():void{
			for(var i:int=0; i<motions.length; i++){
				(motions[i] as MotionDetails).motion.stop();
			}
			clearListeners();
		}
		protected function addListeners():void{
			clearListeners();
			for(var i:int=0; i<motions.length; i++){
				var details:MotionDetails = (motions[i] as MotionDetails);
				if(!isNaN(details.motion.duration) && (!longestMotion || longestMotion.duration<details.motion.duration)){
					longestMotion = details.motion;
				}
			}
			if(longestMotion){
				longestMotion.addEventListener(AtomicMotionEvent.VALUE_CHANGE,eventBubble);
				longestMotion.addEventListener(AtomicMotionEvent.MOTION_END,onMotionEnd);
			}
		}
		protected function clearListeners():void{
			if(longestMotion){
				longestMotion.removeEventListener(AtomicMotionEvent.VALUE_CHANGE,eventBubble);
				longestMotion.removeEventListener(AtomicMotionEvent.MOTION_END,onMotionEnd);
				longestMotion = null;
			}
		}
		public function setValue(prop:String,to:Number):void{
			var details:MotionDetails = motions[findDetails(prop)];
			if(details){
				details.motion.value = to;
				addListeners();
			}
		}
		public function setDestination(prop:String,to:Number):void{
			var details:MotionDetails = motions[findDetails(prop)];
			if(details){
				details.motion.destination = to;
				addListeners();
			}
		}
		
		private function stealMotion(prop:String):MotionDetails{
			var index:Number = findDetails(prop);
			if(!isNaN(index)){
				var details:MotionDetails = motions[index];
				motions.splice(index,1);
				if(details.motion==longestMotion)addListeners();
				return details;
			}
			return null;
		}
		private function findDetails(prop:String):Number{
			for(var i:int=0; i<motions.length; i++){
				var details:MotionDetails = (motions[i] as MotionDetails);
				if(details.prop==prop)return i;
			}
			return NaN;
		}
		private function eventBubble(e:Event):void{
			dispatchEvent(e);
		}
		private function onMotionEnd(e:Event):void{
			clearListeners();
			dispatchEvent(e);
		}
	}
}

import org.tbyrne.motion.AtomicMotion;
import org.tbyrne.motion.AtomicMotionEvent;
	
class MotionDetails{
	public var motion:AtomicMotion;
	public var prop:String;
	private var subject:Object;
	
	public function MotionDetails(subject:Object,motion:AtomicMotion,prop:String){
		this.subject = subject;
		this.prop = prop;
		this.motion = motion;
		motion.addEventListener(AtomicMotionEvent.VALUE_CHANGE,onChange);
	}
	private function onChange(e:AtomicMotionEvent):void{
		try{
			subject[prop] = motion.value;
		}catch(e:Error){}
	}
}